module utils.vector3;

import std.string;
import std.math;

public struct Vector3(T) {
    T x1 = 0; //initialization better than nan
    T x2 = 0;
    T x3 = 0;

    alias x1 x;
    alias x2 y;
    alias x3 z;

    alias T Type;

    public static Vector3 opCall(T x1, T x2, T x3) {
        Vector3 ret;
        ret.x1 = x1;
        ret.x2 = x2;
        ret.x3 = x3;
        return ret;
    }

    public static Vector3 fromSpherical(T theta, T phi, T r) {
        Vector3 res;
        res.x = cast(T)(sin(theta)*cos(phi));
        res.y = cast(T)cos(theta);
        res.z = cast(T)(sin(theta)*sin(phi));
        return res;
    };

    public Vector3 opAdd(Vector3 v) {
        return Vector3(x1+v.x1, x2+v.x2, x3+v.x3);
    }

    public void opAddAssign(Vector3 v) {
        x1 += v.x1;
        x2 += v.x2;
        x3 += v.x3;
    }

    public Vector3 opSub(Vector3 v) {
        return Vector3(x1-v.x1, x2-v.x2, x3-v.x3);
    }

    ///dot product
    public T opMul(Vector3 v) {
        return x1*v.x1 + x2*v.x2 + x3*v.x3;
    }

    public Vector3 opMul(T scalar) {
        return Vector3(x1*scalar, x2*scalar, x3*scalar);
    }
    public Vector3 opMul_r(T scalar) {
        return opMul(scalar);
    }
    public void opMulAssign(T scalar) {
        x1 *= scalar;
        x2 *= scalar;
        x3 *= scalar;
    }

    public Vector3 opDiv(T scalar) {
        return Vector3(x1/scalar, x2/scalar, x3/scalar);
    }

    public void opDivAssign(T scalar) {
        x1 /= scalar;
        x2 /= scalar;
        x3 /= scalar;
    }

    ///cross product
    public Vector3 opOr(Vector3 v) {
        return Vector3(y*v.z - z*v.y, z*v.x - x*v.z, x*v.y - y*v.x);
    }

    public Vector3 opNeg() {
        return Vector3(-x1, -x2, -x3);
    }

    public T quad_length() {
        return x*x + y*y + z*z;
    }

    public T length() {
        return cast(T)sqrt(cast(real)(x*x + y*y + z*z));
    }

    //doesn't make any sense with T==int
    public Vector3 normal() {
        T len = length();
        return Vector3(cast(T)(x/len), cast(T)(y/len), cast(T)(z/len));
    }

    public void length(T new_length) {
        //xxx might be numerically stupid (especially with integers...)
        *this = normal*new_length;
    }

    public void add_length(T add) {
        *this = normal*(length+add);
    }

    public void swap(inout Vector3 other) {
        Vector3 tmp = other;
        other = *this;
        *this = tmp;
    }

    public T opIndex(uint idx) {
        assert(idx < 3);
        return *(&x1 + idx);
    }

    public bool isNan() {
        return isnan(x1) || isnan(x2) || isnan(x3);
    }

    public void opIndexAssign(T value, uint idx) {
        switch (idx) {
            case 0: x1 = value; return;
            case 1: x2 = value; return;
            case 2: x3 = value; return;
            default:
                throw new Exception("out of bounds");
        }
    }

    public char[] toString() {
        return "("~std.string.toString(x1)~", "~std.string.toString(x2)~", "~std.string.toString(x3)~")";
    }
}

public alias Vector3!(int) Vector3i;
public alias Vector3!(float) Vector3f;

//Subtraction and cross product that do not allocate new memory

public void sub3f(out Vector3f dest, inout Vector3f v1, inout Vector3f v2) {
    dest.x1 = v1.x1 - v2.x1;
    dest.x2 = v1.x2 - v2.x2;
    dest.x3 = v1.x3 - v2.x3;
}

public void cross3f(out Vector3f dest, inout Vector3f v1, inout Vector3f v2) {
    dest.x1 = v1.x2*v2.x3 - v1.x3*v2.x2;
    dest.x2 = v1.x3*v2.x1 - v1.x1*v2.x3;
    dest.x3 = v1.x1*v2.x2 - v1.x2*v2.x1;
}
